﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Scheduling.Models.Helpers
{
    public class LinqHelper
    {
        /// <summary>
        /// Maps two simple objects copying similar names. Only one layer of copying is performed. 
        /// </summary>
        /// <typeparam name="T">Type of source object. </typeparam>
        /// <typeparam name="T2">Type of target object. </typeparam>
        /// <param name="source">Source object. </param>
        /// <param name="target">Target object. </param>
        public static void MapAttributes<T, T2>(T source, T2 target)
        {
            foreach (var sprop in typeof(T).GetProperties())
            {
                foreach (var tprop in typeof(T2).GetProperties())
                {
                    if (sprop.Name == tprop.Name)
                    {
                        tprop.SetValue(target, sprop.GetValue(source, null), null);
                    }
                }
            }
        }

        /// <summary>
        /// Maps two simple lists of objects copying similar names. Only one layer of copying is performed. 
        /// </summary>
        /// <typeparam name="T">Type of source object. </typeparam>
        /// <typeparam name="T2">Type of target object. </typeparam>
        /// <param name="source">The source object. </param>
        /// <returns>The target object. </returns>
        public static List<T2> MapList<T, T2>(List<T> source) where T2 : new()
        {
            var result = new List<T2>();
            foreach (var o in source)
            {
                T2 t = new T2();
                MapAttributes<T, T2>(o, t);
                result.Add(t);
            }
            return result;
        }

        /// <summary>
        /// Performs a Generic Two-Layer map between a object and a LINQ query. 
        /// Does not support Many-to-Many relations. 
        /// </summary>
        /// <typeparam name="T">The type to map the LINQ data to. </typeparam>
        /// <param name="q">The LINQ result. </param>
        /// <param name="mappings">A list of mappings using the Source>Target Syntax. 
        /// First target is root object and should never have a mapping. An example: 
        /// { "u", "ud>Department" }
        /// </param>
        /// <returns>A mapped list of the specified object. </returns>
        public static List<T> GenericMap<T>(IQueryable q, params string[] mappings) where T : new()
        {
            List<T> result = new List<T>();
            foreach (var i in q)
            {
                T obj = new T();
                foreach (var prop in i.GetType().GetProperties())
                {
                    string mapping = "";
                    foreach (var s in mappings)
                    {
                        string[] tokens = s.Split('>');
                        if (s != mappings.First() && tokens[0] == prop.Name)
                            mapping = tokens[1];
                    }
                    if (mapping == "") //Simple Mappings
                    {
                        foreach (var ps in prop.PropertyType.GetProperties())
                        {
                            foreach (var pt in typeof(T).GetProperties())
                            {
                                if (ps.Name == pt.Name && pt.PropertyType == ps.PropertyType)
                                {
                                    pt.SetValue(obj, ps.GetValue(prop.GetValue(i, null), null), null);
                                }
                            }
                        }
                    }
                    else //Second Level Mappings
                    {
                        PropertyInfo subObjProp = null;
                        PropertyInfo subSrcProp = prop;
                        foreach (var p in typeof(T).GetProperties())
                        {
                            if (p.Name == mapping)
                            {
                                subObjProp = p;
                            }
                        }
                        if (subObjProp == null) throw new Exception("LINQ ORM: Objects not compatible");

                        var subObj = Activator.CreateInstance(subObjProp.PropertyType);
                        var subSrc = prop.GetValue(i, null);

                        bool die = false;
                        foreach (var ps in subSrcProp.PropertyType.GetProperties())
                        {
                            foreach (var pt in subObjProp.PropertyType.GetProperties())
                            {
                                if (ps.Name == pt.Name && pt.PropertyType == ps.PropertyType)
                                {
                                    if (subSrc == null)
                                        die = true;
                                    else 
                                        pt.SetValue(subObj, ps.GetValue(subSrc, null), null);
                                }
                            }
                        }
                        if (die == true) continue;

                        //Insert Tetriary IDs
                        foreach (var pt in subObjProp.PropertyType.GetProperties())
                        {
                            if (pt.GetValue(subObj, null) == null && !pt.PropertyType.IsSealed)
                            {
                                var tetriaryObj = Activator.CreateInstance(pt.PropertyType);
                                foreach (var p in subSrcProp.PropertyType.GetProperties())
                                {
                                    foreach (var p2 in pt.PropertyType.GetProperties())
                                    {
                                        if ((p2.Name == "ID") && p.Name == pt.PropertyType.Name + "ID")
                                        {
                                            p2.SetValue(tetriaryObj, p.GetValue(subSrc, null), null);
                                        }
                                    }
                                }
                                pt.SetValue(subObj, tetriaryObj, null);
                            }
                        }

                        subObjProp.SetValue(obj, subObj, null);
                    }
                }
                result.Add(obj);
            }
            return result;
        }
    }
}
